<?php
//
//  Solfege
//
//  Created by Jules Bernable on 2007-07-25.
//  Copyright (c) 2007  __LaForceMolle__. All rights reserved.
//
require_once 'org/forcemolle/Utils.php';
Utils::loadFunction(array(
	'array/array_slip',
	'array/array_search_r'
));
//
require_once 'MusicTheory.php';
//
//
//
class Solfege {
	//
	/********************************************************************************
	******************************** Propriétés *************************************
	********************************************************************************/
	//
	var $THEORY;
	//
	var $root = 0;
	//
	/********************************************************************************
	********************************** Constructeur *********************************
	********************************************************************************/
	// constructeur appelé par php4. 
	function Solfege(){
		$this->__construct();
	}
	// constructeur appelé par php5.
	function __construct(){
		$this->THEORY = new MusicTheory();
	}
	//
	/********************************************************************************
	*********************************** Methodes ************************************
	********************************************************************************/
	//
	//************************************//
	// NOTES METHODS
	//************************************//
	//
	function set_root($root){
		if(is_string($root)){
			$root_key = $this->note_to_offset($root);
			if($root_key === false){
				echo 'Error: string misformated... <br>';
				return;
			}
			$this->root = $root_key;
		}else if(is_numeric($root)){
			if(!array_key_exists($root, $this->THEORY->TONES)){
				echo 'Error: wrong key... <br>';
				return;		
			}
			$this->root = $root;
		}
	}
	//
	function get_root(){
		return $this->offset_to_note($this->root);
	}
	//
	function get_enharmonics($tone_key){
		$low = $this->offset_to_note( $tone_key - $this->THEORY->INTERVALS['enharmonic'] );
		$high = $this->offset_to_note( $tone_key + $this->THEORY->INTERVALS['enharmonic'] );
		return array($low, $high);
	}
	//
	//************************************//
	// MODES METHODS
	//************************************//
	//
	function get_mode($mode){
		$ton = $this->get_mode_relative($mode);
		//
		foreach ($this->THEORY->MODES[$mode]['degrees']['cadential_notes'] as $cadential_degree) {
			$cadential_notes[] =  $ton['roots'][$cadential_degree-1]['name'];
		}
		//
		for($i = 0; $i<count($ton['chords']); $i++){
			$degree = $i + 1;
			$ton['roots'][$i]['cadential'] = $this->is_cadential_note($degree, $mode);
			$ton['chords'][$i]['non_modal'] = $this->is_non_modal_chord($ton['chords'][$i]['name']);
			$ton['chords'][$i]['cadential'] = $this->is_cadential_chord($degree, $mode);
			//$ton['chords'][$i]['extensions'] = 
			for($j=0; $j<count($ton['chords'][$i]['notes']); $j++) {
				$note_name = $ton['chords'][$i]['notes'][$j]['name'];
				$ton['chords'][$i]['notes'][$j]['cadential'] = (in_array($note_name, $cadential_notes))?true:false;
			}
		}
		return $ton;
	}
	//
	function get_mode_relative($mode){
		$temp = $this->get_parent_mode($mode);
		$ton['parent'] = $temp['parent'];
		$ton['roots'] = array_slip($temp['roots'], $this->THEORY->MODES[$mode]['degree'] - 1);
		$ton['chords'] = array_slip($temp['chords'], $this->THEORY->MODES[$mode]['degree'] - 1);
		$ton['cadential_notes'] = $temp['cadential_notes'];
		return $ton;
	}
	//
	function get_parent_mode($mode){
		$relative_mode =  $this->THEORY->MODES[$mode]['parent'];
		$relative_root = $this->root - $this->THEORY->MODES[$mode]['offset'];
		$ton = $this->get_mode_parallel($relative_root, $relative_mode);
		$ton['name'] = $this->toHTML($this->offset_to_note($relative_root)).' '.$relative_mode;
		$ton['root'] = $this->offset_to_note($relative_root);
		return $ton;
	}
	//
	function get_mode_parallel($root, $mode){
		$roots = $this->THEORY->MODES[$mode]['degrees']['roots'];
		$chords = $this->THEORY->MODES[$mode]['degrees']['chords'];
		$exts = $this->THEORY->MODES[$mode]['degrees']['extensions'];
		for ($i = 0; $i<count($roots); $i++){ 
			$degree = $roots[$i];
			$real_degree = $degree + $root;
			$ton['roots'][$i]['name'] = $this->offset_to_note($real_degree);
			//
			$chord = $chords[$i];
			if($chord == 'X'){
				$ton['chords'][$i] = array(
					'name' => ' unknown ',
					'notes' => array(),
					'extensions' => array()
				);
			}else{
				$ton['chords'][$i] = array(
					'name' => $chord,
					'notes' => $this->get_chord_tones($real_degree, $chord),
					'extensions' => $this->get_chord_extensions($real_degree, $exts[$i])
				);
			}
			//
		}
		$ton['parent'] = $this->THEORY->MODES[$mode]['parent'];
		$ton['cadential_notes'] = $this->THEORY->MODES[$mode]['degrees']['cadential_notes'];
		return $ton;
	}
	//
	function get_modes_from_chord($root, $chord){
		$parent_modes = $this->get_parent_modes_from_chord($root, $chord);
		if(is_array($parent_modes['results'])){
			foreach ($parent_modes['results'] as $name => $parent_mode) {
				$temp['parent_root'] = $parent_mode['parent_root'];
				$temp['chord'] = $chord;
				$temp['degree'] = $parent_mode['degree'];
				$child_mode = $this->get_mode_by_degree($root, $parent_mode['degree'], $parent_mode['parent_mode']);
				$ret[] = array_merge($temp, $child_mode);
			}
			return $ret;
		}
		return false;
	}
	//
	function get_parent_modes_from_chord($root, $chord){
		$matches['root'] = $this->offset_to_note($root);
		$matches['chord'] = $chord;
		foreach ($this->THEORY->PARENT_MODES as $name => $parent_mode) {
			if(in_array($chord, $parent_mode['degrees']['chords'])){
				for ($i = 0; $i<count($parent_mode['degrees']['chords']) ; $i++){ 
					$current_chord = $parent_mode['degrees']['chords'][$i];
					if($current_chord == $chord){
						$current_root = $parent_mode['degrees']['roots'][$i];
						$root_key = $root;
						$match = array(
							'parent_mode' => $name,
							'parent_root' => $this->offset_to_note($root_key - $current_root),
							'degree' => $i+1,
						);
						$matches['results'][] = $match;
					}
				}
			}
		}
		return $matches;
	}
	//
	function get_mode_by_degree($root, $degree, $parent_mode){
		foreach($this->THEORY->MODES as $name => $mode){
			if($mode['parent']==$parent_mode && $mode['degree']==$degree){
				$temp['root'] = $this->offset_to_note($root);		
				$temp['name'] = $name;
				$results = array_merge($temp, $this->get_mode($name));
			}
		}
		return $results;
	}
	//
	function get_chord_tones($root, $chord){
		$tones = $this->THEORY->CHORDS[$chord];
		foreach ($tones as $tone) {
			$temp[] = array( 'name' => $this->offset_to_note($root+$tone) );
		}
		return $temp;
	}
	//
	function get_chord_extensions($root, $extensions){
		$ext_names = array_flip($this->THEORY->EXTENSIONS);
		foreach($extensions as $extension){
			$temp[] = array(
				'note' => $this->offset_to_note($root+$extension),
				'name' => $ext_names[$extension]
			);
		}
		return $temp;
	}
	//
	//************************************//
	// Utility METHODS
	//************************************//
	//
	function is_cadential_note($degree, $mode){
		return (in_array($degree, $this->THEORY->MODES[$mode]['degrees']['cadential_notes']) ) ? true : false;
	}
	//
	function is_cadential_chord($degree, $mode){
		return (in_array($degree, $this->THEORY->MODES[$mode]['degrees']['cadential_chords']) ) ? true : false;
	}
	//
	function is_non_modal_chord($chord_name){
		return ( in_array($chord_name, $this->THEORY->NON_MODAL_CHORDS) ) ? true : false;
	}
	//
	function transpose_up($tone, $steps){
		return $this->transpose($tone, $steps, 'UP');
	}
	//
	function transpose_down($tone, $steps){
		return $this->transpose($tone, $steps, 'DOWN');
	}
	//
	function transpose($tone, $steps, $method='UP'){
		if(array_key_exists($steps, $this->THEORY->INTERVALS)){
			$steps = $this->THEORY->INTERVALS[$steps];
			$tone_key = $this->note_to_offset($tone);
			switch ($method) {
				case 'UP':
					$new_key = $tone_key + $steps;
					break;
				case 'DOWN':
					$new_key = $tone_key - $steps;
					break;
				default:
					break;
			}
			$new_tone = $this->offset_to_note($tone_key+$steps);
			return $new_tone;
		}
		return false;
	}
	//
	function get_interval($lower_note_name, $upper_note_name, $flag='ASCENDING'){
		$lower_note_offset = $this->note_to_offset($lower_note_name);
		$upper_note_offset = $this->note_to_offset($upper_note_name);
		switch ($flag) {
			case 'ASCENDING':
				$int = $lower_note_offset + $upper_note_offset;
				break;
			case 'DESCENDING':
				$int = $lower_note_offset - $upper_note_offset;
				break;	
			default:
				# code...
				break;
		}
		return array_search($int, $this->THEORY->INTERVALS);
	}
	//
	function interval_to_degree($int, $mode){
		return array_search($this->interval_to_offset($int), $this->THEORY->MODES[$mode]['degrees']['roots']);
	}
	//
	function interval_to_offset($int){
		return $this->THEORY->INTERVALS[$int];
	}
	//
	function offset_to_note($offset){
		if(is_numeric($offset)) return $this->THEORY->TONES[$offset];
		return false;
	}
	function note_to_offset($note){
		if(is_string($note)) return array_search($note, $this->THEORY->TONES);
		return false;
	}
	//
	function mode_name_to_HTML($mode_name){
		foreach ($this->THEORY->ENTITIES['alterations'] as $key => $value) {
			$key = preg_quote($key);
			// prevent from replacing the '#' character in html entities
			$patterns[] = "/(?<!&)$key(?=\d)/";
			$replacements[] = "$value";
		}
		//
		return preg_replace($patterns, $replacements, $mode_name);
	}
	//
	function toHTML($str, $use_short_symbols=true){
		if ($use_short_symbols){
			foreach ($this->THEORY->ENTITIES['short_symbols'] as $key => $value) {
				$key = preg_quote($key);
				$ss_patterns[] = "$key";
				$ss_replacements[] = "$value";
			}
			$str = str_replace($ss_patterns, $ss_replacements, $str);
		}
		foreach ($this->THEORY->ENTITIES['alterations'] as $key => $value) {
			switch($key){
				case 'n':
					// because n is not used for note names
					$pattern = "/$key(?=\d)/";
					break;
				case '#':
					// prevents from replacing the '#' character in html entities
					$pattern = "/(?<!&)$key/";
					break;
				default:
					$pattern = "/$key/";
					break;
			}
			$patterns[] = $pattern;
			$replacements[] = "<sup>$value</sup>";
		}
		//
		return preg_replace($patterns, $replacements, $str);
	}
	//
}
?>